package cn.ronghuanet.car.service.impl;

import cn.ronghuanet.base.constants.BaseConstans;
import cn.ronghuanet.car.domain.CarType;
import cn.ronghuanet.car.mapper.CarTypeMapper;
import cn.ronghuanet.car.service.ICarTypeService;
import cn.ronghuanet.base.service.impl.BaseServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.Serializable;
import java.util.*;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author xiong
 * @since 2023-06-30
 */
@Service
public class CarTypeServiceImpl extends BaseServiceImpl<CarType> implements ICarTypeService {

    @Autowired
    private CarTypeMapper carTypeMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 查询车辆类型树
     * 方案一: 递归  查询n+1次数据库
     * 方案二:循环   查询1次数据库  ->  对于数据库的压力更小
     * 不是说递归方案不能使用,而是递归方案不适合用在要查询数据库的时候
     *         // 缓存穿透?  缓存中没有数据,数据库中也没有数据,会导致在查询时所有的请求都会打到数据库,会给数据库造成压力
     *         // 1  非法请求参数限制
     *         // 2 如果数据库中没有,就缓存一个默认值到缓存中  实现这个
     *         // 3 使用布隆过滤器来解决
     * 缓存击穿: 缓存中某个热点数据突然过期,大量请求过来访问的时候,因为缓存中没有就会全部打到数据库,
     * 1 采用互斥锁的方案,就会将操作数据库加锁,保证一次只能一个请求去访问数据库 - 采纳这种
     * 2 不给热点数据设置过期时间
     * @return
     */
    @Override
    public List<CarType> tree(Long pid) {
        // 1 先从redis中查询车辆类型数据
        Object obj = redisTemplate.opsForValue().get(BaseConstans.CAR_TYPE_TREE_REDIS_KEY);

        // 2 判断redis中有没有数据
        // 2.1 如果有,直接返回
        if(Objects.nonNull(obj)){
            System.out.println("从缓存中获取数据.................");
            return (List<CarType>)obj;
        }
        synchronized (CarTypeServiceImpl.class){ // 1W并发同时过来访问数据库  1 2
            obj = redisTemplate.opsForValue().get(BaseConstans.CAR_TYPE_TREE_REDIS_KEY);
            if(Objects.nonNull(obj)){
                return (List<CarType>)obj;
            }
            // 2.2 如果没有,就要查询数据库获取数据,并存入redis
            System.out.println("从数据库中获取数据...................");
            List<CarType> types = getCarTypesByLoop(pid);
            //List<CarType> types = null; // 模拟数据库没有数据的情况
            if(types != null){
                redisTemplate.opsForValue().set(BaseConstans.CAR_TYPE_TREE_REDIS_KEY,types);
            }else{
                redisTemplate.opsForValue().set(BaseConstans.CAR_TYPE_TREE_REDIS_KEY,new ArrayList<>());
            }
            return types;
        }
    }

    /**
     * 循环方案实现类型树查询  查询1次数据库
     * @param pid
     * @return
     */
    private List<CarType> getCarTypesByLoop(Long pid){
        // 1 查询出所有的车辆类型
        List<CarType> carTypes = carTypeMapper.selectAll();
        // 将所有的车辆类型放入map中,以类型的id作为key,以类型对象作为Value存入map
        Map<Long,CarType> map = new HashMap<>();
        for (CarType carType : carTypes) {
            map.put(carType.getId(),carType);
        }

        // 定义要返回的集合 - 它放的是一级类型
        List<CarType> result = new ArrayList<>();

        // 2 循环所有的车辆类型
        for (CarType carType : carTypes) {
            // 2.1 如果pid=0,将它放入要返回的集合中
            if(carType.getPid().longValue() == pid.longValue()){
                result.add(carType);
            }else{
                // 2.2 如果pid != 0,找到它的父类型,将自己作为父类型的子类型放进去
                // 方案一:父类型也在所有的类型中,所以循环所有的类型,判断carType.getPid == 类型.id,就说明这是它的父类型
                //  时间复杂度:  n*n次  简单理解:代码中的循环次数
                /*for (CarType parent : carTypes) {
                    if(carType.getPid().longValue() == parent.getId().longValue()){
                        // 将自己放入父类型的子集合里面
                        parent.getChildren().add(carType);
                        break;      // 如果都找到自己的父类型的话,没必要继续往下循环的
                    }
                }*/
                // 方案二:从map中取得父类型   时间复杂度:2n次
                CarType parent = map.get(carType.getPid());
                parent.getChildren().add(carType);
            }
        }
        return result;
    }

    /**
     * 递归方案实现类型树查询
     * @param pid
     * @return
     */
    private List<CarType> getCarTypesByRecursion(Long pid) {
        // 1 查询出所有的一级类型
        List<CarType> carTypes = carTypeMapper.loadByPid(pid);
        // 如果没有子类型,返回null
        if(carTypes == null || carTypes.size() == 0){
            return null;
        }
        for (CarType carType : carTypes) {
            // 循环父类型,以父类型的ID作为pid,查询它下面的子类型
            List<CarType> childs = tree(carType.getId());
            carType.setChildren(childs);
        }
        return carTypes;
    }

    /**
     * 当数据做增删改操作时,需要同步更新redis中的数据,否则会导致redis和数据库数据不一致
     * 如果我们是更新redis中的数据的话
     *      要先操作数据库,再操作redis,因为如果先操作redis,万一操作数据库时报错了,数据库数据是没有改变的,而redis改变了
     * 做完增删改操作以后,直接删除redis中的数据   采纳这种方案
     * @param o
     */
    @Transactional
    @Override
    public void insert(CarType o) {
        super.insert(o);
        // 删除redis中的数据,在查询再次往里面存放数据
        redisTemplate.delete(BaseConstans.CAR_TYPE_TREE_REDIS_KEY);
    }

    @Transactional
    @Override
    public void update(CarType carType) {
        super.update(carType);
        // 删除redis中的数据,在查询再次往里面存放数据
        redisTemplate.delete(BaseConstans.CAR_TYPE_TREE_REDIS_KEY);
    }

    @Transactional
    @Override
    public void delete(Serializable id) {
        super.delete(id);
        // 删除redis中的数据,在查询再次往里面存放数据
        redisTemplate.delete(BaseConstans.CAR_TYPE_TREE_REDIS_KEY);
    }
}
